package com.mini.gateway.core.modules.route.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.mini.gateway.core.filter.ElapsedTimeGatewayFilterFactory;
import com.mini.gateway.core.modules.route.dto.RouteDefined;
import com.mini.gateway.core.modules.route.dto.RouteView;
import com.mini.gateway.core.modules.route.entity.MiniRoute;
import com.mini.gateway.core.modules.route.mapper.RouteMapper;
import com.mini.gateway.plugin.exception.BusinessException;
import com.mini.gateway.core.modules.route.entity.RouteParam;
import com.mini.gateway.core.modules.route.enums.ParamType;
import com.mini.gateway.core.modules.route.enums.RouteType;
import com.mini.gateway.core.modules.route.mapper.RouteParamMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

/**
 * RouteService
 *
 */
@Component
public class RouteService {

    @Autowired
    private RouteMapper routeMapper;
    @Autowired
    private RouteParamMapper routeParamMapper;
    @Autowired
    private InstanceCache instanceCache;
    @Autowired
    private ApplicationEventPublisher publisher;
    @Autowired
    private MiniRouteDefinitionLocator miniRouteDefinitionLocator;

    /**
     * 获取所有数据库路由配置
     * @return
     */
    public List<RouteDefined> getRoutes() {
        List<RouteDefined> definedList = new ArrayList<>();

        QueryWrapper<MiniRoute> wrapper = new QueryWrapper<>();
        wrapper.lambda().orderByAsc(MiniRoute::getOrders, MiniRoute::getPath);
        List<MiniRoute> routeList = routeMapper.selectList(wrapper);
        if (CollectionUtils.isNotEmpty(routeList)) {
            for (MiniRoute _route : routeList) {
                RouteDefined defined = new RouteDefined(_route);
                // Filter
                List<RouteParam> filterList = routeParamMapper.selectListByRouteId(_route.getId(), ParamType.FILTER);
                if (CollectionUtils.isNotEmpty(filterList)) {
                    for (RouteParam _param : filterList) {
                        defined.addFilter(_param.getName(), _param.getArgs());
                    }
                }
                definedList.add(defined);
            }
        }
        return definedList;
    }

    /**
     * 添加路由配置
     * @param route
     * @param enableHystrix
     * @param timeoutInMilliseconds
     * @param enableLog
     */
    @Transactional
    public void addRoute(MiniRoute route, boolean enableHystrix, int timeoutInMilliseconds, boolean enableLog) {
        add(route);
        if (enableHystrix) {
            RouteParam param = new RouteParam();
            param.setRouteId(route.getId());
            param.setParamType(ParamType.FILTER);
            param.setName(NameUtils.normalizeFilterFactoryName(HystrixGatewayFilterFactory.class));
            param.addArgs("name", route.getName());
//            param.addArgs("fallbackUri", "forward:/gateway/fallback");
//            param.addArgs("timeoutInMilliseconds", String.valueOf(timeoutInMilliseconds));
            routeParamMapper.save(param);
        }
        if (enableLog) {
            RouteParam param = new RouteParam();
            param.setRouteId(route.getId());
            param.setParamType(ParamType.FILTER);
            param.setName(NameUtils.normalizeFilterFactoryName(ElapsedTimeGatewayFilterFactory.class));
            param.addArgs("name", route.getName());
            routeParamMapper.save(param);
        }
        refresh();
    }

    /**
     * 新增Route
     * @param route
     */
    public void addRoute(MiniRoute route) {
        add(route);
        // 新增成功后，直接触发刷新路由
        refresh();
    }
    /**
     * 新增Route至数据库
     * @param route
     */
    private void add(MiniRoute route) {
        MiniRoute _route = getByName(route.getName());
        if (_route == null) {
            routeMapper.insert(route);
        }
    }

    /**
     * 根据名称查询路由
     * @param name
     * @return
     */
    private MiniRoute getByName(String name) {
        QueryWrapper<MiniRoute> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(MiniRoute::getName, name);
        return routeMapper.selectOne(wrapper);
    }

    /**
     * 更新路由
     * @param id
     * @param path
     * @param uri
     * @param orders
     */
    public void update(Integer id, String path, String uri, int orders, boolean enableHystrix, int hystrixValue, boolean enableLog) {
        MiniRoute route = routeMapper.selectById(id);
        UpdateWrapper<MiniRoute> wrapper = new UpdateWrapper<>();
        if (StringUtils.isNotEmpty(uri) && !uri.equals(route.getUri()) && !uri.startsWith("lb://")) {
            try {
                URI.create(uri);
            } catch (Exception ex) {
                throw new BusinessException("uri地址格式错误!");
            }
        }
        wrapper.lambda().set(MiniRoute::getPath, path)
                .set(MiniRoute::getUri, uri)
                .set(MiniRoute::getOrders, orders)
                .eq(MiniRoute::getId, id);
        routeMapper.update(null, wrapper);

        RouteParam hystrixParam = getRouteParam(route.getId(), ParamType.FILTER, NameUtils.normalizeFilterFactoryName(HystrixGatewayFilterFactory.class));
        if (enableHystrix && hystrixParam == null) {
                hystrixParam = new RouteParam();
                hystrixParam.setRouteId(route.getId());
                hystrixParam.setParamType(ParamType.FILTER);
                hystrixParam.setName(NameUtils.normalizeFilterFactoryName(HystrixGatewayFilterFactory.class));
                hystrixParam.addArgs("name", route.getName());
//                hystrixParam.addArgs("fallbackUri", "forward:/gateway/fallback");
//                hystrixParam.addArgs("hystrix.command.tenbent-access-rest.execution.isolation.thread.timeoutInMilliseconds", String.valueOf(10000));
                routeParamMapper.save(hystrixParam);
        } else if (!enableHystrix && hystrixParam != null){
            routeParamMapper.deleteById(hystrixParam.getId());
        }

        RouteParam param = getRouteParam(route.getId(), ParamType.FILTER, NameUtils.normalizeFilterFactoryName(ElapsedTimeGatewayFilterFactory.class));
        if (enableLog && param == null) {
            param = new RouteParam();
            param.setRouteId(route.getId());
            param.setParamType(ParamType.FILTER);
            param.setName(NameUtils.normalizeFilterFactoryName(ElapsedTimeGatewayFilterFactory.class));
            param.addArgs("name", route.getName());
            routeParamMapper.save(param);
        } else if (!enableLog && param != null){
            routeParamMapper.deleteById(param.getId());
        }
        refresh();
    }

    /**
     * 刷新路由， 发布刷新事件
     */
    private void refresh() {
        miniRouteDefinitionLocator.loadRouteList(true);
        // 发布刷新事件， springCloud gateway处理
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     *
     * @param id
     * @return
     */
    public MiniRoute getRoute(Integer id) {
        return routeMapper.selectById(id);
    }

    public MiniRoute getRoute(String routeId) {
        QueryWrapper<MiniRoute> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(MiniRoute::getName, routeId);
        return routeMapper.selectOne(wrapper);
    }

    /**
     * 获取路由展示
     * @return
     */
    public List<RouteView> getRouteList() {
        List<RouteView> resultList = new ArrayList<>();

        List<RouteDefined> routeList = miniRouteDefinitionLocator.getCacheList();
        for (RouteDefined route : routeList) {
            RouteView view = new RouteView();
            view.setId(route.getId());
            view.setName(route.getName());
            view.setPath(route.getPath());
            view.setOrder(route.getOrders());
            view.setUri(route.getUri());
            view.setRouteType(route.getType());
            // 服务发现类型，获取服务实例
            if (RouteType.DISCOVERY.equals(route.getType())) {
                List<ServiceInstance> instanceList = instanceCache.cache().get(route.getName());
                if (CollectionUtils.isNotEmpty(instanceList)) {
                    for (ServiceInstance instance : instanceList) {
                        view.addInstance(instance.getUri().toString());
                    }
                }
            }
            resultList.add(view);
        }
        return resultList;
    }

    /**
     * 获取参数配置
     * @param routeId
     * @param type
     * @param name
     * @return
     */
    private RouteParam getRouteParam(Integer routeId, ParamType type, String name) {
        QueryWrapper<RouteParam> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(RouteParam::getRouteId, routeId)
                .eq(RouteParam::getParamType, type)
                .eq(RouteParam::getName, name);
        return routeParamMapper.selectOne(wrapper);
    }

    /**
     * 获取参数配置
     * @param routeId
     * @param type
     * @return
     */
    public List<RouteParam> getRouteParamList(Integer routeId, ParamType type) {
        QueryWrapper<RouteParam> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(RouteParam::getRouteId, routeId)
                .eq(RouteParam::getParamType, type);
        return routeParamMapper.selectList(wrapper);
    }

    /**
     * 获取路由名称集合
     * @return
     */
    public List<String> getRouteNameList() {
        QueryWrapper<MiniRoute> wrapper = new QueryWrapper<>();
        wrapper.lambda().orderByAsc(MiniRoute::getOrders, MiniRoute::getPath);
        List<MiniRoute> routeList = routeMapper.selectList(wrapper);
        List<String> resultList = new ArrayList<>();
        for (MiniRoute route : routeList) {
            resultList.add(route.getName());
        }
        return resultList;
    }
}
